org 0x900

jmp CODE16_START

; 段属性的一些定义，暂时定义一些目前用得到的属性
DA_32       equ    0x4000  ; D/B = 1
DA_DR       equ    0x90    ; 数据，只读
DA_DRW      equ    0x92    ; 数据，可读/写
DA_DRWA     equ    0x93    ; 数据，可读/写，已访问
DA_C        equ    0x98    ; 代码，仅执行
DA_CR       equ    0x9A    ; 代码，可执行，可读
DA_CCO      equ    0x9C    ; 代码，一致性段，仅执行
DA_CCOR     equ    0x9E    ; 代码，一致性段，可执行，可读，已访问
DA_LDT      equ    0x82    ; TYPE : LDT

; 门属性的一些定义
DA_CALL_GATE  equ     0x8C    ; 32 位调用门
DA_TSS        equ	  0x89	  ; 32 位 TSS (可用)

; 段选择符属性定义
SA_RPL0     equ    0     ; RPL = 0
SA_RPL1     equ    1     ; RPL = 1
SA_RPL2     equ    2     ; RPL = 2
SA_RPL3     equ    3     ; RPL = 3
SA_TIG      equ    0     ; TI = 0, GDT
SA_TIL      equ    4     ; TI = 1, LDT

; 段描述符中 DPL 属性定义（段描述符高 32 位的 bit13-bit14 ）
DA_DPL0     equ    0x00  ; DPL = 0
DA_DPL1     equ    0x20  ; DPL = 1
DA_DPL2     equ    0x40  ; DPL = 2
DA_DPL3     equ    0x60  ; DPL = 3


; 段描述符定义
%macro Descriptor 3         ; 有三个参数：段基址、段界限、段属性
    dw  %2 & 0xFFFF         ; 段界限 1  (2 字节)
    dw  %1 & 0xFFFF         ; 段基址 1  (2 字节)
    db  (%1 >> 16) & 0xFF   ; 段基址 2  (1 字节)
    dw  ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF) ; 段属性 1 + 段界限 2 + 段属性 2 (2 字节)
    db  (%1 >> 24) & 0xFF   ; 段基址 3 (1 字节)
%endmacro                   ; 共 8 个字节

; 门描述符定义
%macro Gate 4               ; 有四个参数：选择子、偏移地址、参数个数、属性
    dw    (%2 & 0xFFFF)                      ; 偏移地址1
    dw    %1                                 ; 选择子
    dw    (%3 & 0x1F) | ((%4 << 8) & 0xFF00) ; 属性
    dw    ((%2 >> 16) & 0xFFFF)              ; 偏移地址2
%endmacro 

; 全局描述符表定义
;                             段基址        段界限                   段属性
GDT_BASE        : Descriptor     0,            0,                      0
CODE32_DESC     : Descriptor     0,            CODE32_SEG_LEN - 1,     DA_C + DA_32
VIDEO_DESC      : Descriptor     0xB8000,      0xBFFFF - 0xB8000,      DA_DRWA + DA_32 + DA_DPL3
DATA_DESC       : Descriptor     0,            DATA_SEG_LEN - 1,       DA_DR + DA_32
STACK32_DESC    : Descriptor     0,            TOP_OF_STACK32,         DA_DRW + DA_32
TASK_A_LDT_DESC : Descriptor     0,            TASK_A_LDT_LEN - 1,     DA_LDT
FUNC_DESC       : Descriptor     0,            FUNC_SEG_LEN - 1,       DA_C + DA_32
TSS_DESC        : Descriptor     0,            TSS_LEN -1,             DA_TSS + DA_DPL0 
; 门描述符定义
;                        选择子，           偏移地址，         参数个数，     属性
FUNC_PRINT_DESC : Gate   FUNC_SELECTOR,    printOffset,       0,            DA_CALL_GATE + DA_DPL3        
; ...
GDT_LEN         equ   $ - GDT_BASE  ; GDT 长度 = 当前地址 - GDT_BASE 地址

GDT_PTR :
    dw   GDT_LEN - 1
    dd   GDT_BASE

; 段选择符定义，RPL = 0; TI = 0
CODE32_SELECTOR        equ   (0x0001 << 3) + SA_RPL0 + SA_TIG
VIDEO_SELECTOR         equ   (0x0002 << 3) + SA_RPL0 + SA_TIG
DATA_SELECTOR          equ   (0x0003 << 3) + SA_RPL0 + SA_TIG
STACK32_SELECTOR       equ   (0x0004 << 3) + SA_RPL0 + SA_TIG
TASK_A_LDT_SELECTOR    equ   (0x0005 << 3) + SA_RPL0 + SA_TIG
FUNC_SELECTOR          equ   (0x0006 << 3) + SA_RPL0 + SA_TIG
TSS_SELECTOR           equ   (0x0007 << 3) + SA_RPL0 + SA_TIG
FUNC_PRINT_SELECTOR    equ   (0x0008 << 3) + SA_RPL3 + SA_TIG

msg db "Loader..."	; 使用 db 定义数据
msgLen equ $-msg	; 字符串 msg 长度


[bits 16]
CODE16_START :
    xor ax, ax		; xor 指令与 and or 指令类似，两个操作数的每一对对应位都应用如下操作原则：
					; 如果两个位的值相同（同为 0 或同为 1），则结果位等于 0；否则结果位等于 1
					; xor ax, ax 等于 ax = 0
	mov ss, ax		; 栈段 ss = 0
	mov ds, ax		; 数据段 ds = 0
	mov es, ax		; 附加数据段 es = 0 
	mov sp, 0x900	; 设置栈，跟 0x7c00 一样，0x900 地址以下也有一段安全可用内存

    ; 设置光标位置 (dl, dh)
	mov ah, 0x02    ; AH 功能号 = 0x02，设置光标
	mov dh, 0x01    ; 设置光标行号
	mov dl, 0x00    ; 设置光标列号
	mov bh, 0x00    ; 页码
	int 0x10

    ; 打印 msg
	mov ax, msg
	mov cx, msgLen
	call print

    ; 初始化段描述符中的段基址
    mov esi, CODE32_START
    mov edi, CODE32_DESC
    call InitDescItem

    mov esi, DATA_SEGMENT
    mov edi, DATA_DESC
    call InitDescItem

    mov esi, STACK32_SEGMENT
    mov edi, STACK32_DESC
    call InitDescItem

    mov esi, TSS_SEGMENT
    mov edi, TSS_DESC
    call InitDescItem

    mov esi, TASK_A_LDT_BASE
    mov edi, TASK_A_LDT_DESC 
    call InitDescItem
    
    mov esi, TASK_A_CODE32_SEGMENT
    mov edi, TASK_A_CODE32_DESC
    call InitDescItem
    
    mov esi, TASK_A_DATA32_SEGMENT
    mov edi, TASK_A_DATA32_DESC
    call InitDescItem
    
    mov esi, TASK_A_STACK32_SEGMENT
    mov edi, TASK_A_STACK32_DESC 
    call InitDescItem

    mov esi, FUNCTION_SEGMENT
    mov edi, FUNC_DESC 
    call InitDescItem
    
    ; 打开 A20 地址线
    in al, 0x92 
    or al, 0000_0010B 
    out 0x92, al

    ; 加载描述符表
    ;mov eax, 0
    ;mov ax, ds
    ;shl eax, 4
    ;add eax, GDT_BASE
    ;mov dword [GDT_PTR + 2], eax
    lgdt [GDT_PTR]

    ; 通知 CPU 进入保护模式，即将 CR0 寄存器的 PE（bit0） 位置 1
    mov eax, cr0
    or eax, 0x01 
    mov cr0, eax

    ; 刷新流水线
    ; 跳转到 32 位代码段继续执行
    jmp dword CODE32_SELECTOR:0


; 参数： esi --> 代码段标签
; 参数： edi --> 段描述符标签
InitDescItem:
    push eax

    mov eax, 0
    mov ax, cs
    shl eax, 4
    add eax, esi
    mov word [edi + 2], ax
    shr eax, 16
    mov byte [edi + 4], al
    mov byte [edi + 7], ah

    pop eax
    ret

; 打印字符串
; ax : 输入参数，字符串地址
; cx : 输入参数，字符串长度
print:
	; 入栈
	push ax
	push bp
	push bx

	mov bp, ax      ; 保存字符串地址
	mov ax, 0x1301  ; 子功能号 0x13 是写字符串
	mov bx, 000fh   ; 页号位 0，黑底白字
	int 0x10  

	; 出栈
	pop bx
	pop bp
	pop ax

	ret 


[bits 32]
CODE32_START :
    ; mov esp, 0x900	; 设置栈
    mov ax, STACK32_SELECTOR
    mov ss, ax
    
    mov eax, TOP_OF_STACK32
    mov esp, eax

    ; 设置显存段
	mov ax, VIDEO_SELECTOR
    mov gs, ax   
    
    ; 设置数据段
    mov ax, DATA_SELECTOR
    mov ds, ax

    mov ebp, msg2Offset
    mov bl, 0x0F        ; 打印属性，黑底白字
    ; 坐标 (0, 2)
    mov dl, 0x00
    mov dh, 0x02
    ; call print_str_32
    call FUNC_PRINT_SELECTOR : 0
    ; call FUNC_SELECTOR : printOffset

    ; 加载任务状态段 TSS
    mov ax, TSS_SELECTOR
    ltr ax

    ; 加载局部段描述符表
    mov ax, TASK_A_LDT_SELECTOR
    lldt ax

    pushf
    pop eax 
    or eax, 0x3000
    push eax
    popf

    ; jmp TASK_A_CODE32_SELECTOR:0
    push TASK_A_STACK32_SELECTOR
    push TASK_A_TOP_OF_STACK32
    push TASK_A_CODE32_SELECTOR
    push 0
    retf

    ; jmp $

  
CODE32_SEG_LEN equ $-CODE32_START


DATA_SEGMENT:
    msg2 db "Enter protection", 0           ; 以 0 为字符串结束标志
    msg2Offset equ  msg2 - DATA_SEGMENT         ; msg2 在数据段 DATA_SEGMENT 中的偏移量
DATA_SEG_LEN equ    $ - DATA_SEGMENT

STACK32_SEGMENT:
    times 1024 * 4 db 0                     ; 开辟 4K 的内存空间当做栈                  
STACK32_SEG_LEN equ    $ - STACK32_SEGMENT
TOP_OF_STACK32  equ    STACK32_SEG_LEN - 1  ; 栈顶 

; 全局函数段
; 全局函数比如系统函数可以放在这个段中
FUNCTION_SEGMENT:
; 使用显存方式打印字符串
; ds:ebp --> 打印的数据起始地址(相对于段基址的偏移地址)
; bl     --> 打印属性
; dx     --> 打印起始坐标 (dl, dh)
print_str_32:
    push ebp
    push eax
    push edi
    push cx
    push dx

  ; 循环  
  s:
    mov cl, [ds:ebp]    ; 取地址 ds:ebp 中的数据存到 cl 寄存器
    cmp cl, 0           ; 比较 cl 是否为 0 
    je end              ; 若为 0 ，就结束打印

    ; 根据坐标 (dl, dh) 计算出偏移量，存入 eax 中
    ; (80 * dh + dl)*2 ; 每行最多显示 80 个字符
    mov eax, 80
    mul dh
    add al, dl
    shl eax, 1          ; eax = eax*2

    mov edi, eax        ; edi ：显存中的偏移量
    mov ah, bl          ; 显示属性
    mov al, cl          ; 要打印的字符
    mov [gs:edi], ax    ; 显示数据放入显存
    inc ebp             ; 自增
    inc dl              ; 自增
    jmp s               ; 循环

; 打印结束
end:
    pop dx
    pop cx
    pop edi
    pop eax
    pop ebp
    
    retf

printOffset equ print_str_32 - FUNCTION_SEGMENT
FUNC_SEG_LEN equ $-FUNCTION_SEGMENT


TSS_SEGMENT:
    dd    0
    dd    TOP_OF_STACK32      ; esp0
    dd    STACK32_SELECTOR    ; ss0
    dd    0                   ; esp1
    dd    0                   ; ss1
    dd    0                   ; esp2
    dd    0                   ; ss2
    times 4 * 18 dd 0
    dw    0
    dw    $ - TSS_SEGMENT + 2
    db    0xFF   
TSS_LEN    equ    $ - TSS_SEGMENT


; ======================================
; Task A code segment
; ======================================

; Task A 局部段描述符表定义
;                                    段基址        段界限                   段属性
TASK_A_LDT_BASE     :
TASK_A_CODE32_DESC  : Descriptor     0,            TASK_A_CODE32_SEG_LEN - 1,     DA_C + DA_32 + DA_DPL3
TASK_A_DATA32_DESC  : Descriptor     0,            TASK_A_DATA32_SEG_LEN - 1,     DA_DR + DA_32 + DA_DPL3
TASK_A_STACK32_DESC : Descriptor     0,            TASK_A_STACK32_SEG_LEN - 1,    DA_DRW + DA_32 + DA_DPL3
; ...
TASK_A_LDT_LEN      equ   $ - TASK_A_LDT_BASE  ; 长度 = 当前地址 - TASK_A_LDT_BASE 地址

; Task A 局部段选择符定义，RPL = 3; TI = 1
TASK_A_CODE32_SELECTOR    equ   (0x0000 << 3) + SA_RPL3 + SA_TIL
TASK_A_DATA32_SELECTOR    equ   (0x0001 << 3) + SA_RPL3 + SA_TIL
TASK_A_STACK32_SELECTOR   equ   (0x0002 << 3) + SA_RPL3 + SA_TIL


TASK_A_DATA32_SEGMENT:
    TASK_A_STRING         db   "Task A", 0
    TASK_A_STRING_OFFSET  equ  TASK_A_STRING - TASK_A_DATA32_SEGMENT ; 相对于段基址的偏移地址
TASK_A_DATA32_SEG_LEN     equ  $ - TASK_A_DATA32_SEGMENT

TASK_A_STACK32_SEGMENT:
    times 1024 db 0
TASK_A_STACK32_SEG_LEN    equ  $ - TASK_A_STACK32_SEGMENT
TASK_A_TOP_OF_STACK32     equ  TASK_A_STACK32_SEG_LEN - 1

TASK_A_CODE32_SEGMENT:
    mov ax, VIDEO_SELECTOR
    mov gs, ax
    
    mov ax, TASK_A_STACK32_SELECTOR
    mov ss, ax
    
    mov eax, TASK_A_TOP_OF_STACK32
    mov esp, eax
    
    mov ax, TASK_A_DATA32_SELECTOR
    mov ds, ax
    
    mov ebp, TASK_A_STRING_OFFSET
    mov bl, 0x0F
    mov dh, 0x03
    mov dl, 0x00

    ; call TaskAPrintString
    call FUNC_PRINT_SELECTOR : 0
    ; call FUNC_SELECTOR : printOffset

    ;pushf
    ;pop eax 
    ;or eax, 0x3000
    ;push eax
    ;popf

    mov al, 0x0A
    out 0x20, al
    
    jmp $
    
TASK_A_CODE32_SEG_LEN   equ  $ - TASK_A_CODE32_SEGMENT
